package com.loong.data.handler;

import com.alibaba.fastjson.JSON;
import com.loong.common.constant.ShopStatus;
import com.loong.common.param.BaseIdParam;
import com.loong.common.rabbitmq.dto.GoodsBaseDTO;
import com.loong.common.rabbitmq.dto.ShopDTO;
import com.loong.common.utils.TransformUtils;
import com.loong.data.common.backend.param.PageParam;
import com.loong.data.common.backend.param.ShopQueryParam;
import com.loong.data.common.backend.result.ShopResult;
import com.loong.data.common.forehead.param.*;
import com.loong.data.common.forehead.result.GoodsForeheadResult;
import com.loong.data.common.forehead.result.ShopAndGoodsForeheadResult;
import com.loong.data.common.forehead.result.ShopForeheadResult;
import com.loong.data.dto.GoodsDTOResult;
import com.loong.data.dto.ShopAndRecommendGoodsDTOResult;
import com.loong.data.dto.ShopDTOResult;
import com.loong.data.model.ShopModel;
import com.loong.data.param.DistanceAndShopIdListRPCParam;
import com.loong.data.repository.ShopRepository;
import com.loong.data.utils.ESPageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.sort.*;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;

import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

/**
 * @Description:
 * @Author: 刘群
 * @Date: 2020/11/11 11:59
 */
@Component
@Slf4j
public class ShopHandler {
    @Resource
    private ShopRepository shopRepository;

    @Resource
    private ElasticsearchRestTemplate template;

    @Resource
    private RestHighLevelClient client;

    @Resource(name = "deliveryPriceCalculator")
    private CalculatorStrategy calculatorStrategy;

    @Resource
    private GoodsHandler goodsHandler;
//    @DubboReference
//    IShopForeheadResultConsumerService iShopForeheadResultConsumerService;

    /**
     * 新增店铺信息到es：MQ消费者中调用该方法
     *
     * @param shopDTO 店铺参数
     */
    public void saveShopModel(ShopDTO shopDTO) {
        ShopModel shopModel = TransformUtils.transform(shopDTO, ShopModel.class);
        buildGeoPoint(shopDTO, shopModel);
        shopRepository.save(shopModel);
    }

    /**
     * 构建GeoPoint对象
     *
     * @param shopDTO   店铺DTO
     * @param shopModel 店铺Model
     */
    private void buildGeoPoint(ShopDTO shopDTO, ShopModel shopModel) {
        if (StringUtils.isNotEmpty(shopDTO.getLatitude()) && StringUtils.isNotEmpty(shopDTO.getLongitude())) {
            double latitude = Double.parseDouble(shopDTO.getLatitude());
            double longitude = Double.parseDouble(shopDTO.getLongitude());
            GeoPoint geoPoint = new GeoPoint(latitude, longitude);
            shopModel.setLocation(geoPoint);
        }
    }

    /**
     * 更新店铺信息到es: MQ消费者中调用该方法
     *
     * @param shopDTO 店铺参数
     */
    public void updateShopModel(ShopDTO shopDTO) {
        //创建更新请求
        UpdateRequest request = new UpdateRequest("shop", shopDTO.getId().toString());
        //将要更新的数据以json格式封装到请求中
        ShopModel shopModel = TransformUtils.transform(shopDTO, ShopModel.class);
        buildGeoPoint(shopDTO, shopModel);
        request.doc(JSON.toJSONString(shopModel), XContentType.JSON);
        //执行更新请求的操作
        try {
            client.update(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据id删除es中的店铺信息：MQ消费者中调用该方法
     *
     * @param id 店铺id
     */
    public void deleteShopModelById(Long id) {
        shopRepository.deleteById(id);
    }

    /**
     * 后台根据店铺id查询店铺
     *
     * @param shopId 店铺id
     * @return 店铺
     */
    public ShopResult findShopModelById(Long shopId) {
        ShopDTOResult shopDTOResult = this.commonFindShopById(shopId);
        if (ObjectUtils.isEmpty(shopDTOResult)) return null;
        return TransformUtils.transform(shopDTOResult, ShopResult.class);
    }

    /**
     * 前台根据店铺id查询店铺
     *
     * @param param 店铺id
     * @return 店铺
     */
    public ShopForeheadResult foreheadFindShopModelById(BaseIdParam param) {
        ShopDTOResult shopDTOResult = this.commonFindShopById(param.getId());
        if (ObjectUtils.isEmpty(shopDTOResult)) return null;
        return TransformUtils.transform(shopDTOResult, ShopForeheadResult.class);
    }

    /**
     * 公共方法：根据id查询店铺
     * RPC接口也调用了该方法
     *
     * @param shopId 店铺id
     * @return 店铺结果
     */
    public ShopDTOResult commonFindShopById(Long shopId) {
        Optional<ShopModel> optional = shopRepository.findById(shopId);
        if (optional.isPresent()) {
            return TransformUtils.transform(optional.get(), ShopDTOResult.class);
        }
        return null;
    }

    /**
     * 后台多条件分页查询店铺
     *
     * @param shopParam 店铺查询参数
     * @param pageParam 分页参数
     * @return 店铺集合
     */
    public List<ShopResult> pageQueryShopModel(ShopQueryParam shopParam, PageParam pageParam) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (ObjectUtils.isNotEmpty(shopParam)) {
            //若店铺查询参数不为空，则封装布尔查询的条件
            ShopQueryForeheadParam shopForeheadParam = TransformUtils.transform(shopParam, ShopQueryForeheadParam.class);
            this.commonPackingBoolQueryByShopParam(shopForeheadParam, boolQuery);
        } else {
            //店铺查询参数为空，则后台可以查询所有的店铺
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        //分页
        PageForeheadParam pageForeheadParam = null;
        if (ObjectUtils.isNotEmpty(pageParam)) {
            pageForeheadParam = TransformUtils.transform(pageParam, PageForeheadParam.class);
        }
        Pageable pageable = ESPageUtil.getPageableByParam(pageForeheadParam);
        //根据分数排序
        ScoreSortBuilder sortBuilder = SortBuilders.scoreSort();
        List<ShopDTO> shopDTOList = this.executeQuery(sortBuilder, boolQuery, pageable);
        if (CollectionUtils.isEmpty(shopDTOList)) return null;
        return TransformUtils.transformList(shopDTOList, ShopResult.class);
    }

    /**
     * 前台根据参数查询附近店铺
     *
     * @param distanceAndShopParam 距离、店铺查询、分页封装的参数
     * @return 店铺集合
     */
    public List<ShopForeheadResult> findShopForeheadByDistance(DistanceAndShopQueryForeheadParam distanceAndShopParam) {
        DistanceForeheadParam param = distanceAndShopParam.getDistanceParam();
        double userLatitude = Double.parseDouble(param.getLatitude());
        double userLongitude = Double.parseDouble(param.getLongitude());
        //创建布尔查询对象
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //前端只能查询出已开业的店铺，对于未审核等店铺不能查询出来
        boolQuery.must(QueryBuilders.termQuery("status", ShopStatus.OPENING));
        //如果店铺查询参数不为空，则根据店铺查询参数封装布尔查询
        ShopQueryForeheadParam shopQueryParam = distanceAndShopParam.getShopQueryParam();
        if (ObjectUtils.isNotEmpty(shopQueryParam)) {
            this.commonPackingBoolQueryByShopParam(shopQueryParam, boolQuery);
        }
        //创建地理位置查询对象
        GeoDistanceQueryBuilder distanceQuery = QueryBuilders.geoDistanceQuery("location");
        //以给定的参数的经纬度为中心
        distanceQuery.point(userLatitude, userLongitude);
        //指定到中心点的距离，并指定单位。若c端没有传距离，则默认是10000km，//TODO 这默认值不能这么大，现在测试暂时使用10000km
        String circleDistance = "10000";
        if (ObjectUtils.isNotEmpty(param.getDistance())) circleDistance = param.getDistance();
        distanceQuery.distance(circleDistance, DistanceUnit.KILOMETERS);
        boolQuery.filter(distanceQuery);//查询过滤

        //创建一个地理位置排序对象
        GeoDistanceSortBuilder sortBuilder = new GeoDistanceSortBuilder("location", userLatitude, userLongitude);
        sortBuilder.unit(DistanceUnit.KILOMETERS);//指定单位
        sortBuilder.order(SortOrder.ASC);//升序
        Pageable pageable = ESPageUtil.getPageableByParam(distanceAndShopParam.getPageParam());

        //执行查询并获得结果
        List<ShopDTO> shopDTOList = executeQuery(sortBuilder, boolQuery, pageable);
        if (CollectionUtils.isEmpty(shopDTOList)) return null;
        //封装距离和配送费到店铺中，并返回结果集
        List<ShopForeheadResult> shopList = TransformUtils.transformList(shopDTOList, ShopForeheadResult.class);
        this.packingDistanceAndDeliveryFeeToShop(shopList,userLatitude,userLongitude);
        return shopList;
    }

    /**
     * 封装距离和配送费到店铺中
     * @param shopList 店铺集合
     * @param userLatitude 用户纬度
     * @param userLongitude 用户经度
     */
    private void packingDistanceAndDeliveryFeeToShop(List<ShopForeheadResult> shopList, Double userLatitude, Double userLongitude) {
        //计算用户地理位置到商家的距离
        for (ShopForeheadResult shop : shopList) {
            double shopLatitude = Double.parseDouble(shop.getLatitude());
            double shopLongitude = Double.parseDouble(shop.getLongitude());
            double distance = GeoDistance.ARC.calculate(userLatitude, userLongitude, shopLatitude, shopLongitude, DistanceUnit.METERS);
            shop.setDistance(Double.toString(distance));
            // 现在配送费暂时改成基础配送费*距离 配送费用向上取整
            BigDecimal basePrice = shop.getBasePrice();
            BigDecimal distance_bigDecimal = new BigDecimal(distance / 1000);
            log.debug("=========basePrice " + basePrice + " distance_bigDecimal" + distance_bigDecimal);
            BigDecimal deliveryPrice = calculatorStrategy.calculate(basePrice, distance_bigDecimal);
            shop.setDeliveryPrice(deliveryPrice);
            //设置店铺是否正在营业中
            long nowTime = new Date().getTime();
            long startTime = shop.getStartTime().getTime();
            long endTime = shop.getEndTime().getTime();
            if (startTime <= nowTime && nowTime <= endTime) {
                shop.setFlag(true);
            }
        }
    }

    /**
     * 前台根据参数搜索店铺或者商品
     *
     * @param param 距离、店铺查询、分页封装的参数
     * @return 店铺携带商品的结果的集合
     */
    public List<ShopAndGoodsForeheadResult> foreheadFindShopOrGoodsByParam(DistanceAndShopQueryForeheadParam param) {
        List<ShopAndGoodsForeheadResult> resultList = new ArrayList<>();
        //查询店铺，这是先根据店铺名称不为空来进行查询的。
        List<ShopForeheadResult> shopList = this.findShopForeheadByDistance(param);
        //搜索名称：c端传入的搜索名称可以为店铺名称，也可以是商品名称
        String searchName = param.getShopQueryParam().getName();
        //根据名称是否查询到店铺，false表示查询到了
        boolean emptyShopList = false;
        if (CollectionUtils.isEmpty(shopList)) {
            //若没查询到店铺，则将店铺名称设置为空再进行查询
            emptyShopList = true;
            param.getShopQueryParam().setName("");
            shopList = this.findShopForeheadByDistance(param);
            //若还是没有查询到店铺，则返回空集合。
            if (CollectionUtils.isEmpty(shopList)) return resultList;
        }
        // 遍历每个店铺并查询出每个店铺下的指定名称的商品
        for (ShopForeheadResult shop : shopList) {
            //封装查询商品的参数：根据店铺id，以及商品名称查询商品
            GoodsAndPageForeheadParam goodsParam = GoodsAndPageForeheadParam.builder()
                    .pageParam(PageForeheadParam.builder().pageSize(1000).pageIndex(1).build())
                    .goodsQueryParam(GoodsQueryForeheadParam.builder()
                            .shopId(shop.getId()) //店铺id
                            .goodsName(searchName) //商品名称
                            .build())
                    .build();
            //获得查询的商品
            List<GoodsBaseDTO> goodsList = goodsHandler.commonPageQueryGoods(goodsParam);
            //根据名称没有查询到店铺
            if (emptyShopList) {
                //同时没有查询到店铺下的商品，则继续下一次的遍历。
                if (CollectionUtils.isEmpty(goodsList)) continue;
            }
            //将店铺和商品集合封装到结果中并返回，有三种结果：
                //1.根据名称查询到了店铺，但是根据名称没有查询到商品。则只返回店铺信息
                //2.根据名称查询到了店铺，同时根据名称也查询到了商品。则同时返回店铺和商品集合信息。
                //3.根据名称没有查询到店铺，但是根据名称查询到了商品。则同时返回店铺和商品集合信息。
            List<GoodsForeheadResult> goodsForeheadList = TransformUtils.transformList(goodsList, GoodsForeheadResult.class);
            ShopAndGoodsForeheadResult result = ShopAndGoodsForeheadResult.builder()
                    .shop(shop)
                    .goodsList(goodsForeheadList)
                    .build();
            resultList.add(result);
        }
        return resultList;
    }

    /**
     * 共有方法：根据店铺查询参数封装布尔查询条件
     *
     * @param shopQueryParam 店铺查询参数
     * @param boolQuery      布尔查询
     */
    private void commonPackingBoolQueryByShopParam(ShopQueryForeheadParam shopQueryParam, BoolQueryBuilder boolQuery) {
        if (ObjectUtils.isNotEmpty(shopQueryParam.getName())) {
            //店铺名称进行模糊查询
            boolQuery.must(QueryBuilders.matchPhraseQuery("name", shopQueryParam.getName()));
        }
        if (ObjectUtils.isNotEmpty(shopQueryParam.getAddress())) {
            //店铺地址进行模糊查询
            boolQuery.must(QueryBuilders.matchPhraseQuery("address", shopQueryParam.getAddress()));
        }
        if (ObjectUtils.isNotEmpty(shopQueryParam.getOpenAllDay())) {
            //店铺是否全天候进行匹配查询
            boolQuery.must(QueryBuilders.matchPhraseQuery("openAllDay", shopQueryParam.getOpenAllDay()));
        }
        if (ObjectUtils.isNotEmpty(shopQueryParam.getStartTime())) {
            //店铺开放的起始时间进行范围查询
            boolQuery.must(QueryBuilders.rangeQuery("startTime").from(shopQueryParam.getStartTime()));
        }
        if (ObjectUtils.isNotEmpty(shopQueryParam.getEndTime())) {
            //店铺开发的结束时间进行范围查询
            boolQuery.must(QueryBuilders.rangeQuery("endTime").to(shopQueryParam.getEndTime()));
        }
        if (ObjectUtils.isNotEmpty(shopQueryParam.getShopTypeId())) {
            //根据店铺分类进行查询
            boolQuery.must(QueryBuilders.termQuery("shopTypeId", shopQueryParam.getShopTypeId()));
        }
    }

    /**
     * 执行查询请求，并获得结果
     *
     * @param queryBuilder 查询条件
     * @param pageable     分页
     * @return 店铺集合
     */
    private List<ShopDTO> executeQuery(SortBuilder sortBuilder, QueryBuilder queryBuilder, Pageable pageable) {
        //存放店铺信息的集合
        List<ShopModel> shopModelList = new ArrayList<>();
        //构建查询条件
        NativeSearchQueryBuilder searchBuilder = new NativeSearchQueryBuilder();
        searchBuilder.withQuery(queryBuilder);
        if (ObjectUtils.isNotEmpty(sortBuilder)) searchBuilder.withSort(sortBuilder);
        if (ObjectUtils.isNotEmpty(pageable)) searchBuilder.withPageable(pageable);

        //获得查询的结果
        SearchHits<ShopModel> hits = template.search(searchBuilder.build(), ShopModel.class);
        List<SearchHit<ShopModel>> hitList = hits.getSearchHits();
        for (SearchHit<ShopModel> hit : hitList) {
            ShopModel shopModel = hit.getContent();
            shopModelList.add(shopModel);
        }
        return TransformUtils.transformList(shopModelList, ShopDTO.class);
    }

    /**
     * RPC接口调用的方法：根据店铺id查询店铺以及以及3个推荐商品
     *
     * @param shopId 店铺id
     * @return 店铺及推荐商品封装的结果
     */
    public ShopAndRecommendGoodsDTOResult foreheadFindShopAndRecommendGoodsById(Long shopId) {
        //根据店铺id查询出店铺
        ShopDTOResult shopDTOResult = this.commonFindShopById(shopId);
        //封装查询参数：根据店铺id，以及商品推荐recommend的值为true查询商品
        GoodsAndPageForeheadParam param = GoodsAndPageForeheadParam.builder()
                .pageParam(PageForeheadParam.builder().pageSize(3).pageIndex(1).build())
                .goodsQueryParam(GoodsQueryForeheadParam.builder().shopId(shopId).recommend(true).build())
                .build();
        List<GoodsBaseDTO> goodsList = goodsHandler.commonPageQueryGoods(param);
        List<GoodsDTOResult> goodsDTOList = TransformUtils.transformList(goodsList, GoodsDTOResult.class);
        return ShopAndRecommendGoodsDTOResult.builder()
                .shopDTOResult(shopDTOResult)
                .goodsDTOList(goodsDTOList)
                .build();
    }

    /**
     * RPC接口调用的方法：根据店铺id集合查询所有的店铺
     * @param param 店铺id集合封装的参数
     * @return 店铺集合
     */
    public List<ShopDTOResult> foreheadFindShopByIdList(DistanceAndShopIdListRPCParam param){
        List<ShopDTOResult> shopList = new ArrayList<>();
        //如果参数为空，则返回空集合
        if (ObjectUtils.isEmpty(param)
                || ObjectUtils.isEmpty(param.getLatitude())
                || ObjectUtils.isEmpty(param.getLongitude())) return shopList;
        //获取经纬度
        double userLatitude = Double.parseDouble(param.getLatitude());
        double userLongitude = Double.parseDouble(param.getLongitude());
        List<Long> shopIdList = param.getShopIdList();
        //创建ids查询查询对象
        IdsQueryBuilder idsQuery = QueryBuilders.idsQuery();
        for (Long id : shopIdList) {
            idsQuery.addIds(id+"");
        }
        //执行查询获得结果
        List<ShopDTO> shopDTOList = this.executeQuery(null, idsQuery, null);
        if (CollectionUtils.isEmpty(shopDTOList)) return shopList;
        //封装距离和配送费到店铺集合中
        List<ShopForeheadResult> shopForeheadList = TransformUtils.transformList(shopDTOList, ShopForeheadResult.class);
        this.packingDistanceAndDeliveryFeeToShop(shopForeheadList,userLatitude,userLongitude);
        //转换结果集
        shopList = TransformUtils.transformList(shopForeheadList,ShopDTOResult.class);
        //根据距离排序
        shopList.sort(Comparator.comparing(ShopDTOResult::getDistance));
        return shopList;
    }
}

